/**
 * 自动下载绳包插件
 * 功能描述：自动从远程仓库下载绳包并解压到指定目录
 * 支持缓存机制、哈希验证和错误处理
 */

// 引入必要的模块
const fs = require('fs').promises;
const fsSync = require('fs');
const http = require('http');
const path = require('path');
const crypto = require('crypto');

// 检查AdmZip依赖是否存在
let AdmZip;
try {
  AdmZip = require('adm-zip');
} catch (error) {
  console.error('错误: 缺少必要的依赖模块 adm-zip');
  AdmZip = class {
    constructor() {}
    extractAllTo() {
      throw new Error('缺少adm-zip依赖');
    }
  };
}

// 常量定义
const DEFAULT_TIMEOUT = 30000; // 下载超时时间(ms)
const FILE_READ_TIMEOUT = 60000; // 文件读取超时时间(ms)
const READ_BUFFER_SIZE = 64 * 1024 * 1024; // 64MB 读取缓冲区
const HASH_ALGORITHM = 'sha256'; // 哈希算法
const LIB_FILE_EXTENSION = '.tlb'; // 绳包文件扩展名

// 全局变量
let remoteRepoUrl = "";

/**
 * 验证路径片段是否安全（不包含路径遍历字符）
 * @param {string} segment 路径片段
 * @returns {boolean} 是否安全
 */
function isValidPathSegment(segment) {
  if (typeof segment !== 'string') {
    return false;
  }
  // 检查是否包含路径遍历字符或空字符串
  return segment.trim() !== '' && 
         !segment.includes('../') && 
         !segment.includes('..\\') && 
         !segment.includes('/') && 
         !segment.includes('\\') && 
         segment !== '.' && 
         segment !== '..';
}

// 插件定义
const cloudLibPlugin = {
  // 插件元数据
  metadata: {
    name: 'cloud-lib-plugin',
    version: '1.0.0',
    description: '自动下载绳包插件',
    author: 'TMake Team',
    isCore: true
  },
  
  // 初始化函数
  init: function(api) {
    /**
       * 设置远程仓库URL
       * @description 配置绳包下载的远程仓库地址
       * @param {Object} node - 节点对象，包含参数信息
       * @param {Array} node.params - 参数数组，第一个参数为远程仓库地址
       * @param {Object} context - 执行上下文
       * @param {Object} plugin - 插件对象
       * @param {string} remoteUrl - 远程仓库地址，从node.params[0]获取
       * @returns {string} - 设置的远程仓库地址
       * @throws {Error} - 当参数不足时抛出错误
       */
      api.registerFunction('设置远程仓库', (node, context) => {
        if (!node.params || node.params.length === 0) {
          throw new Error('设置远程仓库函数需要一个参数,{0}为远程仓库地址');
        }
        remoteRepoUrl = node.params[0]?.value || node.params[0];
        return remoteRepoUrl;
      });

    /**
       * 添加绳包到项目
       * @description 从远程仓库下载绳包并解压到指定目录，支持缓存机制和哈希验证
       * @param {Object} node - 节点对象，包含参数信息
       * @param {Array} node.params - 参数数组，[0]为作者/组织名, [1]为绳包名称, [2]为绳包版本号
       * @param {Object} context - 执行上下文
       * @param {Object} plugin - 插件对象
       * @param {string} author - 作者/组织名，从node.params[0]获取
       * @param {string} libName - 绳包名称，从node.params[1]获取
       * @param {string} version - 绳包版本号，从node.params[2]获取
       * @returns {Promise<string>} - 成功下载的绳包名称
       * @throws {Error} - 当参数不足、参数非法或下载失败时抛出错误
       */
      api.registerFunction('添加绳包', async (node, context) => {
        try {
          if (!node.params || node.params.length < 3) {
            throw new Error('下载绳包函数需要3个参数,{0}为作者/组织名,{1}为绳包名称,{2}为绳包版本号');
          }
          
          // 检查远程仓库URL是否已设置
          if (!remoteRepoUrl) {
            throw new Error('请先调用设置远程仓库函数');
          }
          
          const author = node.params[0]?.value || node.params[0];
          const libName = node.params[1]?.value || node.params[1];
          const version = node.params[2]?.value || node.params[2];
          
          // 安全检查：验证参数不包含路径遍历字符
          if (!isValidPathSegment(author) || !isValidPathSegment(libName) || !isValidPathSegment(version)) {
            throw new Error('参数包含非法字符');
          }
          
          // 结构化的缓存路径
          const cacheDir = path.join(api.getProjectDir(), '.cache', 'downloads', author, libName, version);
          const filePath = path.join(cacheDir, `${libName}${LIB_FILE_EXTENSION}`);
          
          // 确保URL格式正确
          let libUrl = path.join(remoteRepoUrl, author, libName, version, `${libName}${LIB_FILE_EXTENSION}`);
          libUrl = libUrl.replace(/\\/g, '/').replace(/\/\//g, '/');
          // 如果URL没有协议，添加http://作为默认
          if (!libUrl.startsWith('http://') && !libUrl.startsWith('https://')) {
            libUrl = 'http://' + libUrl;
          }

        // 检查文件是否已经存在并且哈希值一致
        if (fsSync.existsSync(filePath)) {
          // 尝试从缓存元数据文件中读取哈希值
          let cachedHash = null;
          const cacheInfoPath = path.join(cacheDir, 'cache-info.json');
          
          if (fsSync.existsSync(cacheInfoPath)) {
            try {
              const cacheInfo = JSON.parse(fsSync.readFileSync(cacheInfoPath, 'utf8'));
              cachedHash = cacheInfo.hash;
            } catch (parseError) {
              console.warn(`${libName} 读取缓存元数据失败: ${parseError.message}`);
            }
          }
          if (!cachedHash) {
            cachedHash = api.getCacheItem(`${libName}-hash`);
            if (cachedHash) {
              console.log(`${libName} 使用API缓存的哈希值进行验证`);
            }
          }
          
          if (cachedHash) {
            try {
              // 异步计算当前文件哈希并比较
              const currentHash = await getFileHash(filePath);
              if (currentHash === cachedHash) {
                console.log(`${libName} 已存在且哈希值一致，跳过下载`);
                
                // 确保目标目录存在
                const outputDir = path.join(api.getProjectDir(), '绳包', author, libName);
                if (!fsSync.existsSync(outputDir)) {
                  console.log(`${libName} 解压文件不存在，重新解压...`);
                  // 如果解压目录不存在，重新解压文件
                  const zipFile = new AdmZip(filePath);
                  if (!fsSync.existsSync(path.dirname(outputDir))) {
                    fsSync.mkdirSync(path.dirname(outputDir), { recursive: true });
                  }
                  zipFile.extractAllTo(outputDir, true);
                }
                
                return libName;
              }
              console.log(`${libName} 哈希值不匹配，需要重新下载`);
            } catch (error) {
              console.error(`${libName} 哈希值计算失败: ${error.message}`);
              // 计算哈希出错，继续执行下载
            }
          }
        }

        // 文件不存在或哈希值不一致，执行下载
        await downloadLibFile(libUrl, filePath, libName, api);
        return libName;
      } catch (error) {
        console.error('添加绳包失败:', error.message);
        // 保持原始错误消息的格式，确保向后兼容性
        if (error.message.includes('下载绳包函数需要3个参数')) {
          throw error;
        }
        // 对于其他错误，重新抛出以保持向后兼容
        throw error;
      }
    });
  }
};

/**
 * 下载绳包文件并计算哈希值
 * @param url 下载地址
 * @param filePath 保存路径
 * @param libName 绳包名称
 * @param api PluginAPI实例
 * @returns {Promise<void>}
 */
function downloadLibFile(url, filePath, libName, api) {
  return new Promise((resolve, reject) => {
    console.log(`开始下载 ${libName}...`);
    
    // 确保远程仓库URL已设置（这里作为额外的安全检查，因为主要检查在入口函数中已完成）
    if (!remoteRepoUrl) {
      return reject(new Error('请先调用设置远程仓库函数'));
    }
    
    // 确保缓存目录存在
    const dirPath = path.dirname(filePath);
    try {
      // 先确保项目根目录下的.cache目录存在
      const cacheRoot = path.join(api.getProjectDir(), '.cache');
      if (!fsSync.existsSync(cacheRoot)) {
        fsSync.mkdirSync(cacheRoot, { recursive: true });
      }
      // 再确保完整的缓存路径存在
      if (!fsSync.existsSync(dirPath)) {
        fsSync.mkdirSync(dirPath, { recursive: true });
      }
    } catch (mkdirError) {
      console.error(`${libName} 创建缓存目录失败: ${mkdirError.message}`);
      return reject(mkdirError);
    }

    const request = http.get(url, async (response) => {
      if (response.statusCode === 200) {
        const fileStream = fsSync.createWriteStream(filePath);
        response.pipe(fileStream);
        
        fileStream.on('finish', async () => {
          try {
            fileStream.close();
            console.log(`${libName} 下载完成`);
            
            // 计算文件哈希并缓存
            const hash = await getFileHash(filePath);
            api.setCacheItem(`${libName}-hash`, hash);
            
            // 解压文件
            try {
              const zipFile = new AdmZip(filePath);
              const outputDir = path.join(api.getProjectDir(), '绳包',libName);
              
              // 确保输出目录存在
              if (!fsSync.existsSync(path.dirname(outputDir))) {
                fsSync.mkdirSync(path.dirname(outputDir), { recursive: true });
              }
              
              zipFile.extractAllTo(outputDir, true);
              console.log(`${libName} 解压完成到 ${outputDir}`);
              
              // 记录缓存信息
              const cacheInfo = {
                libName,
                author,
                version,
                hash,
                timestamp: new Date().toISOString()
              };
              
              try {
                // 保存缓存元数据
                const cacheInfoPath = path.join(cacheDir, 'cache-info.json');
                fsSync.writeFileSync(cacheInfoPath, JSON.stringify(cacheInfo, null, 2));
              } catch (metaError) {
                // 元数据保存失败不影响主要功能，只记录日志
                console.warn(`${libName} 保存缓存元数据失败: ${metaError.message}`);
              }
              
              resolve();
            } catch (extractError) {
              console.error(`${libName} 解压失败: ${extractError.message}`);
              reject(extractError);
            }
          } catch (error) {
            console.error(`${libName} 处理失败: ${error.message}`);
            reject(error);
          }
        });
        
        fileStream.on('error', (streamError) => {
          console.error(`${libName} 文件写入失败: ${streamError.message}`);
          reject(streamError);
        });
      } else {
        const errorMsg = `${libName} 下载失败,状态码: ${response.statusCode}`;
        console.error(errorMsg);
        reject(new Error(errorMsg));
      }
    });

    request.on('error', (error) => {
      console.error(`${libName} 下载出错: ${error.message}`);
      reject(error);
    });
    
    // 设置超时处理
    request.setTimeout(DEFAULT_TIMEOUT, () => {
      const errorMsg = `${libName} 下载超时`;
      console.error(errorMsg);
      request.abort();
      reject(new Error(errorMsg));
    });
  });
}

/**
 * 计算文件的SHA-256哈希值
 * @param filePath 文件路径
 * @returns {Promise<string>} 文件的哈希值
 */
function getFileHash(filePath) {
  return new Promise((resolve, reject) => {
    try {
      // 检查文件是否存在
      if (!fsSync.existsSync(filePath)) {
        return reject(new Error(`文件不存在: ${filePath}`));
      }
      
      // 检查文件是否可访问
      try {
        fsSync.accessSync(filePath, fsSync.constants.R_OK);
      } catch (accessError) {
        return reject(new Error(`无法读取文件: ${filePath}`));
      }
      
      const hash = crypto.createHash(HASH_ALGORITHM);
      const stream = fsSync.createReadStream(filePath, {
        highWaterMark: READ_BUFFER_SIZE,
        encoding: null
      });

      stream.on('error', (error) => {
        console.error(`读取文件失败 ${filePath}: ${error.message}`);
        stream.destroy();
        reject(error);
      });

      stream.on('data', (chunk) => {
        hash.update(chunk);
      });

      stream.on('end', () => {
        try {
          const fileHash = hash.digest('hex');
          resolve(fileHash);
        } catch (digestError) {
          reject(new Error(`计算哈希值失败: ${digestError.message}`));
        }
      });
      
      // 设置读取超时
      stream.setTimeout(FILE_READ_TIMEOUT, () => {
        stream.destroy();
        reject(new Error(`读取文件超时: ${filePath}`));
      });
      
    } catch (error) {
      console.error(`计算哈希值过程出错: ${error.message}`);
      reject(error);
    }
  });
}

// 导出插件
module.exports = cloudLibPlugin;